#include "icestream.h"

ICEStream* ICEStream::alarm_icestrm = 0;

void catch_alarm( int sig )
{
    ICEStream::alarm_icestrm->timeout();
    signal( sig, catch_alarm );
}

ICEStream::ICEStream( NiceAgent* agent, guint stream_id, guint n_component )
{
    this->agent = agent;
    this->stream_id = stream_id;
    this->n_component = n_component;
    rcv_cb = 0;

    pthread_mutex_init( &gathering_candidates_mutex, NULL );
    pthread_mutex_init( &selecting_pair_mutex, NULL );
    pthread_mutex_lock( &gathering_candidates_mutex );
    pthread_mutex_lock( &selecting_pair_mutex );

    signal( SIGALRM, catch_alarm );

    gchar *username, *password;
    nice_agent_get_local_credentials( agent, stream_id, &username, &password );
    local_username = username;
    local_password = password;
    g_free( username );
    g_free( password );

    std::cout << std::endl;
    std::cout << "[Stream " << stream_id << "] local username: " << local_username << std::endl;
    std::cout << "[Stream " << stream_id << "] local password: " << local_password << std::endl;

    candidate_protocol[NICE_CANDIDATE_TRANSPORT_UDP] = "udp";

    candidate_type[NICE_CANDIDATE_TYPE_HOST] = "host";
    candidate_type[NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE] = "srflx";
    candidate_type[NICE_CANDIDATE_TYPE_PEER_REFLEXIVE] = "prflx";
    candidate_type[NICE_CANDIDATE_TYPE_RELAYED] = "relay";
}

ICEStream::~ICEStream()
{
    pthread_mutex_unlock( &gathering_candidates_mutex );
    pthread_mutex_unlock( &selecting_pair_mutex );
    pthread_mutex_destroy( &gathering_candidates_mutex );
    pthread_mutex_destroy( &selecting_pair_mutex );
}

void ICEStream::release()
{
    nice_agent_remove_stream( agent, stream_id );
}

std::vector<ICECandidate> ICEStream::getLocalCandidatesFromComponent( guint component_id )
{
    // Start gathering candidates and wait until the signal candidate-gathering-done is fired
    std::cout << "Obtendo candidatos do componente " << component_id << "..." << std::endl;
    nice_agent_gather_candidates( agent, stream_id );
    pthread_mutex_lock( &gathering_candidates_mutex );
    std::cout << "Candidatos obtidos!" << std::endl;
    return niceCandidateToICECandidate( nice_agent_get_local_candidates( agent, stream_id, component_id ) );
}

std::vector<ICECandidate> ICEStream::getLocalCandidates()
{
    std::vector<ICECandidate> candidates;

    for( guint component_id = 1; component_id <= n_component; component_id++ )
    {
        std::vector<ICECandidate> comp_candidates = getLocalCandidatesFromComponent( component_id );
        for( unsigned int i = 0; i < comp_candidates.size(); i++ )
            candidates.push_back( comp_candidates[i] );
    }

    return candidates;
}

void ICEStream::setRemoteCandidates( std::vector<ICECandidate> rcands )
{
    // Get the list of lists of candidates grouped by component_id
    std::vector<GSList*> cands = iceCandidateToNiceCandidate( rcands );

    for( unsigned int i = 0; i < cands.size(); i++ )
    {
        NiceCandidate* cand = (NiceCandidate*)cands[i]->data;
        guint comp_id = cand->component_id;

        // Wait until the signal new-selected-pair is fired
        std::cout << "Selecionando par para o componente " << comp_id << "..." << std::endl;
        nice_agent_set_remote_candidates( agent, stream_id, comp_id, cands[i] );
        ICEStream::alarm_icestrm = this;
        timeout_error = false;
        alarm( 5 );
        pthread_mutex_lock( &selecting_pair_mutex );
        alarm( 0 );
        if( timeout_error )
            std::cout << "ERRO: Timeout!" << std::endl;
        else
            std::cout << "Par selecionado!" << std::endl;
    }
}

void ICEStream::timeout()
{
    timeout_error = true;
    pthread_mutex_unlock( &selecting_pair_mutex );
}

void ICEStream::send( guint size, gchar* data, guint component_id )
{
    // Send our message
    std::cout << "Enviando a mensagem" << std::endl;
    nice_agent_send( agent, stream_id, component_id, size, data );
}
void ICEStream::receive( guint size, gchar* data, guint component_id )
{
    if( rcv_cb )
        rcv_cb( size, data, component_id );
}

void ICEStream::candidateGatheringDone()
{
    pthread_mutex_unlock( &gathering_candidates_mutex );;
}

void ICEStream::newSelectedPair()
{
    pthread_mutex_unlock( &selecting_pair_mutex );
}

void ICEStream::freeCandidatesGSList( GSList* cands )
{
    for( GSList* cand = cands; cand; cand = cand->next )
        nice_candidate_free( (NiceCandidate*) cand->data );
    g_slist_free( cands );
}

std::vector<ICECandidate> ICEStream::niceCandidateToICECandidate( GSList* cands )
{
    std::vector<ICECandidate> candidates;

    for( ; cands; cands = cands->next )
    {
        NiceCandidate* cand = (NiceCandidate*)cands->data;

        char ip[16]; nice_address_to_string( &cand->addr, ip );
        unsigned int port = nice_address_get_port( &cand->addr );

        char rel_addr[16]; nice_address_to_string( &cand->base_addr, rel_addr );
        unsigned int rel_port = nice_address_get_port( &cand->base_addr );

        ICECandidate candidate;

        candidate.setComponent( cand->component_id );
        candidate.setFoundation( cand->foundation );
        candidate.setIp( ip );
        candidate.setNetwork( 0 ); // FIXME
        candidate.setPort( port );
        candidate.setPriority( cand->priority );
        candidate.setProtocol( candidate_protocol[cand->transport] );
        candidate.setRelAddr( rel_addr );
        candidate.setRelPort( rel_port );
        candidate.setType( candidate_type[cand->type] );

        candidates.push_back( candidate );
    }

    return candidates;
}

std::vector<GSList*> ICEStream::iceCandidateToNiceCandidate( std::vector<ICECandidate> cands )
{
    std::vector<GSList*> candidates;

    for( unsigned int i = cands.size(); i > 0; )
    {
        i--;

        NiceCandidateType type;
        if( candidate_type[NICE_CANDIDATE_TYPE_HOST] == cands[i].getType() )
            type = NICE_CANDIDATE_TYPE_HOST;
        if( candidate_type[NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE] == cands[i].getType() )
            type = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
        if( candidate_type[NICE_CANDIDATE_TYPE_PEER_REFLEXIVE] == cands[i].getType() )
            type = NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
        if( candidate_type[NICE_CANDIDATE_TYPE_RELAYED] == cands[i].getType() )
            type = NICE_CANDIDATE_TYPE_RELAYED;

        NiceCandidate* candidate = nice_candidate_new( type );

        candidate->transport = NICE_CANDIDATE_TRANSPORT_UDP;

        nice_address_init( &candidate->addr );
        nice_address_set_from_string( &candidate->addr, cands[i].getIp().c_str() );
        nice_address_set_port( &candidate->addr, cands[i].getPort() );

        nice_address_init( &candidate->base_addr );
        nice_address_set_from_string( &candidate->base_addr, cands[i].getRelAddr().c_str() );
        nice_address_set_port( &candidate->base_addr, cands[i].getRelPort() );

        candidate->priority = cands[i].getPriority();
        candidate->component_id = cands[i].getComponent();
        std::strcpy( candidate->foundation, cands[i].getFoundation().c_str() );
        
        unsigned int candidates_size = candidates.size();

        for( unsigned int j = 0; j <= candidates_size; j++ )
        {
            if( j == candidates_size )
            {
                candidates.push_back( NULL );
                candidates[j] = g_slist_prepend( candidates[j], candidate );
            }
            else
            {
                NiceCandidate* cand = (NiceCandidate*)candidates[j]->data;
                if( candidate->component_id == cand->component_id )
                {
                    candidates[j] = g_slist_prepend( candidates[j], candidate );
                    break;
                }
            }
        }
    }

    return candidates;
}

std::string ICEStream::getLocalUsername()
{
    return local_username;
}

std::string ICEStream::getLocalPassword()
{
    return local_password;
}

void ICEStream::setRemoteCredentials( std::string remote_username, std::string remote_password )
{
    nice_agent_set_remote_credentials( agent, stream_id, remote_username.c_str(), remote_password.c_str() );
}

void ICEStream::setReceiveCallback( receive_callback rcv_cb )
{
    this->rcv_cb = rcv_cb;
}
